/**
 * 
 */
package qy.qyalgotrader.mystrategy;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections4.MapUtils;
import org.apache.commons.collections4.map.ListOrderedMap;
import org.apache.commons.lang3.exception.ExceptionUtils;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import com.google.common.math.DoubleMath;

import qy.jalgotrade.bar.Bar;
import qy.jalgotrade.bar.Bars;
import qy.jalgotrade.barfeed.BaseBarFeed;
import qy.jalgotrade.broker.Order;
import qy.jalgotrade.broker.backtesting.BacktestingBroker;
import qy.jalgotrade.broker.backtesting.TradePercentage;
import qy.jalgotrade.broker.fillstrategy.DefaultStrategy;
import qy.jalgotrade.broker.fillstrategy.FillStrategy;
import qy.jalgotrade.dataseries.BarDataSeries;
import qy.jalgotrade.dataseries.DataSeries;
import qy.jalgotrade.dataseries.SequenceDataSeries;
import qy.jalgotrade.strategy.BacktestingStrategy;
import qy.jalgotrade.strategy.position.Position;
import qy.jalgotrade.talibext.Indicator;
import qy.jalgotrade.technical.ATR;
import qy.jalgotrade.technical.BollingerBands;
import qy.jalgotrade.technical.CrossUils;
import qy.jalgotrade.technical.MACD;
import qy.jalgotrade.utils.CommonUtils;
import qy.qyalgotrader.mystrategy.StratUtils.MySignalMode;
import qy.qyalgotrader.utils.CommonDataUtils;

/**
 * @author c-geo
 *
 */
public class MacdTrader extends BacktestingStrategy {

	// @formatter:off
	public static final Map<String, Object> DEFAULT_OPT_PARAMS = MapUtils.putAll(new ListOrderedMap<>(), new Object[][] {
		{"signalFreq", null},
		{"bBandsPeriod", 20}, {"numStdDev", 2.0}, // BOLL
		{"atrPeriod", 26}, // ATR
		{"fastEma", 12}, {"slowEma", 26}, {"signalEma", 9}, {"diffSignal", false}, // MACD
		{"useMaxDdSl", true}, {"maxDdAtr", 2.0}, {"maxDdSlAbs", false}, // 止盈 / 止损参数
		{"signalMode", MySignalMode.DEFAULT}, {"loggerOff", false}, // 其他参数
		// 回测模拟 Broker 参数:
		{"cashOrBrokerParams", MapUtils.putAll(new ListOrderedMap<>(), new Object[][] {
			{"initialCash", 1000000.0},
			{"commission", 0.0,},
			{"allowNegativeCash", true},
			{"fillStrategy.noVolumeLimit", true}
		})},
	});

	/**
	 * 标的:
	 */
	private List<String> __instruments;

	/**
	 * 信号模式: 多 / 空 / 多空
	 */
	private MySignalMode __signalMode;

	/**
	 * 策略的信号时间周期, 即主时间周期:
	 */
	private Bar.Frequency __signalFreq;

	// BOLL:
	private int __bBandsPeriod; private double __numStdDev;

	// ATR:
	private int __atrPeriod;

	// MACD:
	private int __fastEma; private int __slowEma; private int __signalEma;
	@SuppressWarnings("unused")
	private boolean __diffSignal;

	// 止盈 / 止损参数:
	private boolean __useMaxDdSl; // 是否使用 最大回撤止盈 / 止损
	private double __maxDdAtr;
	private boolean __maxDdSlAbs; // 是否在头寸产生亏损时执行
	// @formatter:on

	private Map<String, BollingerBands> bbandsDict;

	private Map<String, ATR> atrDict;

	private Map<String, MACD> macdDict;

	// 其他参数:
	private boolean __loggerOff;

	/**
	 * 
	 * @param barFeed
	 * @param instruments
	 * @throws Exception
	 */
	public MacdTrader(BaseBarFeed barFeed, List<String> instruments) throws Exception {

		this(barFeed, instruments, DEFAULT_OPT_PARAMS);
	}

	/**
	 * 
	 * @param barFeed
	 * @param instruments
	 * @param optParams
	 * @throws Exception
	 */
	public MacdTrader(BaseBarFeed barFeed, List<String> instruments, Map<String, Object> optParams) throws Exception {

		super();

		// 初始化回测 Broker:
		@SuppressWarnings("unchecked")
		Map<String, Object> cashOrBrokerParams = (Map<String, Object>) optParams.getOrDefault("cashOrBrokerParams",
		        DEFAULT_OPT_PARAMS.get("cashOrBrokerParams"));
		BacktestingBroker broker = new BacktestingBroker((double) cashOrBrokerParams.get("initialCash"), barFeed,
		        new TradePercentage((double) cashOrBrokerParams.get("commission")));
		broker.setAllowNegativeCash((boolean) cashOrBrokerParams.get("allowNegativeCash"));
		if ((boolean) cashOrBrokerParams.get("fillStrategy.noVolumeLimit")) {
			FillStrategy bfs = broker.getFillStrategy();
			((DefaultStrategy) bfs).setNoVolumeLimit();
		}

		_init(barFeed, broker);

		__instruments = instruments; // 标的
		__signalMode = (MySignalMode) optParams.getOrDefault("singalMode", DEFAULT_OPT_PARAMS.get("singalMode")); // 信号模式: 多 / 空 / 多空
		__signalFreq = (Bar.Frequency) optParams.getOrDefault("signalFreq", DEFAULT_OPT_PARAMS.get("signalFreq")); // 策略的信号时间周期, 即主时间周期
		if (__signalFreq == null) {
			__signalFreq = getFeed().getFrequency();
		}
		// 指标参数:
		// BOLL:
		__bBandsPeriod = (int) optParams.getOrDefault("bBandsPeriod", DEFAULT_OPT_PARAMS.get("bBandsPeriod"));
		__numStdDev = (double) optParams.getOrDefault("numStdDev", DEFAULT_OPT_PARAMS.get("numStdDev"));
		// ATR:
		__atrPeriod = (int) optParams.getOrDefault("atrPeriod", DEFAULT_OPT_PARAMS.get("atrPeriod"));
		// MACD:
		__fastEma = (int) optParams.getOrDefault("fastEma", DEFAULT_OPT_PARAMS.get("fastEma"));
		__slowEma = (int) optParams.getOrDefault("slowEma", DEFAULT_OPT_PARAMS.get("slowEma"));
		__signalEma = (int) optParams.getOrDefault("signalEma", DEFAULT_OPT_PARAMS.get("signalEma"));
		__diffSignal = (boolean) optParams.getOrDefault("diffSignal", DEFAULT_OPT_PARAMS.get("diffSignal"));
		// 是否使用 最大回撤止盈 / 止损:
		__useMaxDdSl = (boolean) optParams.getOrDefault("useMaxDdSl", DEFAULT_OPT_PARAMS.get("useMaxDdSl"));
		__maxDdAtr = (double) optParams.getOrDefault("maxDdAtr", DEFAULT_OPT_PARAMS.get("maxDdAtr"));
		__maxDdSlAbs = (boolean) optParams.getOrDefault("maxDdSlAbs", DEFAULT_OPT_PARAMS.get("maxDdSlAbs"));

		bbandsDict = new HashMap<>();
		atrDict = new HashMap<>();
		macdDict = new HashMap<>();

		for (String e : __instruments) {
			bbandsDict.put(e,
			        new BollingerBands(
			                (SequenceDataSeries<Double>) ((BarDataSeries) barFeed.__getitem__(e)).getCloseDataSeries(),
			                __bBandsPeriod, __numStdDev));
			atrDict.put(e, new ATR((BarDataSeries) barFeed.__getitem__(e), __atrPeriod));
			macdDict.put(e,
			        new MACD((SequenceDataSeries<Double>) ((BarDataSeries) barFeed.__getitem__(e)).getCloseDataSeries(),
			                __fastEma, __slowEma, __signalEma));
		}

		__loggerOff = (boolean) optParams.getOrDefault("loggerOff", DEFAULT_OPT_PARAMS.get("loggerOff"));
	}

	/**
	 * 
	 * @return
	 */
	public Map<String, BollingerBands> getBbandsDict() {

		return bbandsDict;
	}

	/**
	 * 
	 * @return
	 */
	public Map<String, ATR> getAtrDict() {

		return atrDict;
	}

	/**
	 * 
	 * @return
	 */
	public Map<String, MACD> getMacdDict() {

		return macdDict;
	}

	/*
	 * (non-Javadoc)
	 * @see qy.jalgotrade.strategy.BaseStrategy#onEnterOk(qy.jalgotrade.strategy.position.Position)
	 */
	@Override
	public void onEnterOk(Position position) {

		if (!__loggerOff) {
			if (getLogger().isWarnEnabled()) {
				warning(String.format("entered %s position: {instrument: %s, shares: %s, avgEnterPrice: %.2f}",
				        position.getShares() > 0 ? "LONG" : "SHORT", position.getInstrument(), position.getShares(),
				        position.getEntryOrder().getAvgFillPrice()));
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * @see qy.jalgotrade.strategy.BaseStrategy#onEnterCanceled(qy.jalgotrade.strategy.position.Position)
	 */
	@Override
	public void onEnterCanceled(Position position) {

	}

	/*
	 * (non-Javadoc)
	 * @see qy.jalgotrade.strategy.BaseStrategy#onExitOk(qy.jalgotrade.strategy.position.Position)
	 */
	@Override
	public void onExitOk(Position position) {

		if (!__loggerOff) {
			if (getLogger().isWarnEnabled()) {
				warning(String.format("exit position: {instrument: %s, shares: %s, avgExitPrice: %.2f}",
				        position.getInstrument(), position.getExitOrder().getQuantity(),
				        position.getExitOrder().getAvgFillPrice()));
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * @see qy.jalgotrade.strategy.BaseStrategy#onExitCanceled(qy.jalgotrade.strategy.position.Position)
	 */
	@Override
	public void onExitCanceled(Position position) {

		// If the exit was canceled, re-submit it.
		try {
			position.exitMarket();
		} catch (Exception e) {
			getLogger().warn("exception occured during exitMarket(): {}", ExceptionUtils.getStackTrace(e));
		}
	}

	/*
	 * (non-Javadoc)
	 * @see qy.jalgotrade.strategy.BaseStrategy#onStart()
	 */
	@Override
	public void onStart() {

		info("MacdTrader running started!");
	}

	/*
	 * (non-Javadoc)
	 * @see qy.jalgotrade.strategy.BaseStrategy#onFinish(qy.jalgotrade.bar.Bars)
	 */
	@Override
	public void onFinish(Bars bars) {

		info("MacdTrader running finished!");
	}

	/*
	 * (non-Javadoc)
	 * @see qy.jalgotrade.strategy.BaseStrategy#onBars(qy.jalgotrade.bar.Bars)
	 */
	@Override
	public void onBars(Bars bars) throws Exception {

		Map<String, Boolean> exitLongSigDict = __exitLongSignal(bars);
		for (String e : bars.getInstruments()) {
			if (exitLongSigDict.containsKey(e) && exitLongSigDict.get(e)) {
				Position longPos = getActivePositionFor(e, Order.Action.BUY);
				if (longPos != null && !longPos.exitActive()) {
					longPos.exitMarket();
				}
			}
		}

		Map<String, Boolean> exitShortSigDict = __exitShortSignal(bars);
		for (String e : bars.getInstruments()) {
			if (exitShortSigDict.containsKey(e) && exitShortSigDict.get(e)) {
				Position shortPos = getActivePositionFor(e, Order.Action.SELL_SHORT);
				if (shortPos != null && !shortPos.exitActive()) {
					shortPos.exitMarket();
				}
			}
		}

		// 检查多 / 空模式:
		if (__signalMode != MySignalMode.SHORT_ONLY) {
			Table<String, String, Object> enterLongSigDict = __enterLongSignal(bars);
			for (String e : bars.getInstruments()) {
				if ((boolean) enterLongSigDict.get(e, "enter") && getActivePositionFor(e, Order.Action.BUY) == null) {
					double qty = CommonDataUtils.getCnFuTradeUnit(e); // 每手合约交易单位 (RMB)
					enterLong(e, qty);
				}
			}
		}

		if (__signalMode != MySignalMode.LONG_ONLY) {
			Table<String, String, Object> enterShortSigDict = __enterShortSignal(bars);
			for (String e : bars.getInstruments()) {
				if ((boolean) enterShortSigDict.get(e, "enter")
				        && getActivePositionFor(e, Order.Action.SELL_SHORT) == null) {
					double qty = CommonDataUtils.getCnFuTradeUnit(e); // 每手合约交易单位 (RMB)
					enterShort(e, qty);
				}
			}
		}
	}

	/**
	 * 
	 * @param bars
	 * @return
	 */
	private Table<String, String, Object> __enterLongSignal(Bars bars) {

		Table<String, String, Object> signalDict = HashBasedTable.create();
		for (String e : bars.getInstruments()) {
			signalDict.put(e, "enter", false);
			signalDict.put(e, "freq", __signalFreq);
		}

		for (String e : bars.getInstruments()) {
			// 检查各技术指标序列均可用: BOLL, ATR, MACD:
			if (Double.isNaN(bbandsDict.get(e).getUpperBand().__getitem__(-1))
			        || Double.isNaN(atrDict.get(e).__getitem__(-1)) || Double.isNaN(macdDict.get(e).__getitem__(-1))) {
				continue;
			}

			// 策略主逻辑: DIF 上穿 0 轴, 开多:
			if (CrossUils.crossAbove(macdDict.get(e), StratUtils.CONST_DS_0) > 0) {
				// 产生多头信号:
				signalDict.put(e, "enter", true);
				signalDict.put(e, "freq", __signalFreq);

				if (!__loggerOff) {
					if (getLogger().isWarnEnabled()) {
						// TODO: wddatautils.get_asset_name(e)
						warning(String.format("[MacdTrader]%s: <%s> enter LONG signal!", CommonDataUtils.toWindCode(e),
						        __signalFreq));
					}
				}
			}
		}

		return signalDict;
	}

	/**
	 * 
	 * @param bars
	 * @return
	 */
	private Map<String, Boolean> __exitLongSignal(Bars bars) {

		Map<String, Boolean> exitSignalDict = new HashMap<>();
		for (String e : bars.getInstruments()) {
			exitSignalDict.put(e, false);
		}

		for (String e : bars.getInstruments()) {
			Position longPos = getActivePositionFor(e, Order.Action.BUY);
			if (longPos != null && !longPos.exitActive()) {
				// 最高点回撤止损:
				if (__useMaxDdSl) {
					double atr = atrDict.get(e).__getitem__(-1);
					DataSeries<Double> closeDs = ((BarDataSeries) getFeed().__getitem__(e)).getCloseDataSeries();
					int ct = closeDs.__len__()
					        - closeDs.getAbsoluteByDateTime(longPos.getEntryOrder().getSubmitDateTime());
					double drawDown;
					if (ct > 1) {
						drawDown = Indicator.MAX(closeDs, ct, ct).getRight() - bars.__getitem__(e).getClose();
					} else {
						drawDown = 0;
					}

					if (DoubleMath.fuzzyCompare(Math.abs(drawDown), __maxDdAtr * atr,
					        CommonUtils.FUZZY_EQUAL_TOLERANCE_MATH) > 0) {
						if (__maxDdSlAbs) {
							// 多头头寸未产生亏损, 则不执行:
							if (longPos.getPnL() < 0) {
								exitSignalDict.put(e, true);
							}
						} else {
							exitSignalDict.put(e, true);
						}

						if (exitSignalDict.get(e)) {
							if (!__loggerOff) {
								if (getLogger().isWarnEnabled()) {
									// TODO: wddatautils.get_asset_name(e)
									warning(String.format(
									        "[MacdTrader]%s: <%s> exit LONG signal (Big-Drawdown, [%.2f ATR])!",
									        CommonDataUtils.toWindCode(e), __signalFreq, Math.abs(drawDown) / atr));
								}
							}
							continue;
						}
					}
				}

				// 策略平仓主逻辑: DIF 下穿 0 轴:
				if (CrossUils.crossBelow(macdDict.get(e), StratUtils.CONST_DS_0) > 0) {
					exitSignalDict.put(e, true);

					if (!__loggerOff) {
						// TODO: wddatautils.get_asset_name(e)
						warning(String.format("[MacdTrader]%s: <%s> exit LONG signal (DIF-Fall-0)!",
						        CommonDataUtils.toWindCode(e), __signalFreq));
					}
				}
			}
		}

		return exitSignalDict;
	}

	/**
	 * 
	 * @param bars
	 * @return
	 */
	private Table<String, String, Object> __enterShortSignal(Bars bars) {

		Table<String, String, Object> signalDict = HashBasedTable.create();
		for (String e : bars.getInstruments()) {
			signalDict.put(e, "enter", false);
			signalDict.put(e, "freq", __signalFreq);
		}

		for (String e : bars.getInstruments()) {
			// 检查各技术指标序列均可用: BOLL, ATR, MACD:
			if (Double.isNaN(bbandsDict.get(e).getUpperBand().__getitem__(-1))
			        || Double.isNaN(atrDict.get(e).__getitem__(-1)) || Double.isNaN(macdDict.get(e).__getitem__(-1))) {
				continue;
			}

			// 策略主逻辑: DIF 下穿 0 轴, 开多:
			if (CrossUils.crossBelow(macdDict.get(e), StratUtils.CONST_DS_0) > 0) {
				// 产生空头信号:
				signalDict.put(e, "enter", true);
				signalDict.put(e, "freq", __signalFreq);

				if (!__loggerOff) {
					if (getLogger().isWarnEnabled()) {
						// TODO: wddatautils.get_asset_name(e)
						warning(String.format("[MacdTrader]%s: <%s> enter SHORT signal!", CommonDataUtils.toWindCode(e),
						        __signalFreq));
					}
				}
			}
		}

		return signalDict;
	}

	/**
	 * 
	 * @param bars
	 * @return
	 */
	private Map<String, Boolean> __exitShortSignal(Bars bars) {

		Map<String, Boolean> exitSignalDict = new HashMap<>();
		for (String e : bars.getInstruments()) {
			exitSignalDict.put(e, false);
		}

		for (String e : bars.getInstruments()) {
			Position shortPos = getActivePositionFor(e, Order.Action.SELL_SHORT);
			if (shortPos != null && !shortPos.exitActive()) {
				// 最低点反弹止损:
				if (__useMaxDdSl) {
					double atr = atrDict.get(e).__getitem__(-1);
					DataSeries<Double> closeDs = ((BarDataSeries) getFeed().__getitem__(e)).getCloseDataSeries();
					int ct = closeDs.__len__()
					        - closeDs.getAbsoluteByDateTime(shortPos.getEntryOrder().getSubmitDateTime());
					double drawDown;
					if (ct > 1) {
						drawDown = Indicator.MIN(closeDs, ct, ct).getRight() - bars.__getitem__(e).getClose();
					} else {
						drawDown = 0;
					}

					if (DoubleMath.fuzzyCompare(Math.abs(drawDown), __maxDdAtr * atr,
					        CommonUtils.FUZZY_EQUAL_TOLERANCE_MATH) > 0) {
						if (__maxDdSlAbs) {
							// 空头头寸未产生亏损, 则不执行:
							if (shortPos.getPnL() < 0) {
								exitSignalDict.put(e, true);
							}
						} else {
							exitSignalDict.put(e, true);
						}

						if (exitSignalDict.get(e)) {
							if (!__loggerOff) {
								if (getLogger().isWarnEnabled()) {
									// TODO: wddatautils.get_asset_name(e)
									warning(String.format(
									        "[MacdTrader]%s: <%s> exit SHORT signal (Big-Rebound, [%.2f ATR])!",
									        CommonDataUtils.toWindCode(e), __signalFreq, Math.abs(drawDown) / atr));
								}
							}
							continue;
						}
					}
				}

				// 策略平仓主逻辑: DIF 上穿 0 轴:
				if (CrossUils.crossAbove(macdDict.get(e), StratUtils.CONST_DS_0) > 0) {

					exitSignalDict.put(e, true);

					if (!__loggerOff) {
						// TODO: wddatautils.get_asset_name(e)
						warning(String.format("[MacdTrader]%s: <%s> exit SHORT signal (DIF-Break-0)!",
						        CommonDataUtils.toWindCode(e), __signalFreq));
					}
				}
			}
		}

		return exitSignalDict;
	}
}
